// ---------------以下是react面试题----------------- //
// react生命周期有哪些
{
  /* 
  React16+ 后有三个生命周期被废除（官方计划在17版本完全删除这个三个函数）
  componentWillMount
  componentWillReceiveProps
  componentWillUpdate

  三个阶段：挂载阶段，更新阶段，卸载阶段
  挂载阶段：
  1、constructor：构造函数，最先被执行，通常在构造函数里，初始化state，或给定义方法绑定this
  2、static getDerivedStateFromProps(nextProps, prevState)
  这是一个静态方法，当props变化的时候，想要修改state，可以使用这个方法。它应返回一个对象来更新 state，返回 null不更新。
  3、render是纯函数，只返回渲染的东西
  4、componentDidMount：组件挂载之后调用，此时我们可以获取DOM操作，服务端请求，订阅等，但是要记得在componentWillUnMount中取消订阅
   
  更新阶段：
  1、getDerivedStateFromProps：此方法在更新挂载阶段可能被调用
  2、shouldComponentUpdate(prevProps, nextState) 有两个参数，表示新的属性props和变化之后的状态state，返回一个布尔值，true表示会触发重新渲染，false表示不会；默认返回true，通常可以利用这个生命周期优化react渲染
  3、render更新阶也会触发
  4、getSnapshotBeforeUpdate(prevProps,PrevState) 这个方法在render之后，有两个参数prevProps和prevState，表示之前的属性props和之前状态state，这个函数有一个返回值，会作为第三个参数传递给componentDidUpdate,如果你不需要返回值，可以返回null，这个方法必须配合componentDidUpdate使用
  5、componentDidUpdate(prevProps,prveState,snapshot)该方法在getSnapshotBeforeUpdate之后被调用，有三个参数，之前的props，之前的state和snapshot；第三个参数是getSnapshotBeforeUpdate的返回值；如果触发某些回掉函数时需要用到DOM元素的状态，则将对比或计算的过程迁移至getSnapshotBeforeUpdate，然后在componentDidUpdate中统一触发回掉或状态更新

  卸载阶段：
  componentWillUnMount当组件卸载或销毁是调用，在这个函数里面我们可以清除定时器，取消网络请求，清理无用DOM元素等垃圾清理工作
  */
}

// react怎么创建虚拟dom的，说一下具体过程
{
  /* 
  createElement 创造出一个js对象 返回一个reactElement
  会先判断是否有config然后处理config 会判断是否有存在defaultProps，接着处理children
  利用reactElement创建react元素
  利用object形式递归生成一个dom树
  */
}

// 说一下你理解的dom diff
{
  /* 
  三种优化策略
    更新的时候只比较同级，并不会跨级对比
    同级对比不对， 整个替换或者删除
    同层对比如果只是换位置的话， 就会复用。 通过key实现的。

  对树的遍历
    先序深度优先遍历 时间复杂度 o(n)

  对比规则
    当节点类型相同时候， 会比较属性是否相同。 产生一个属性的补丁包 {type: 'ATTRS', attrs: {class : 'xxx'}}
    新的DOM节点不存在的时候 {type: 'REMOVE', index: xx}
    节点类型不相同的时候， 直接替换  {type: 'REPLACE', newNode: newNode}
    文本的变化  {type: 'TEXT', text: 1}

  setState
    在组件已经渲染完成中，setState是同步执行的，但是不会立马更新，因为他在批量处理中会等待组件render才真正触发。不在批处理中的任务可能会立马更新。到底更新不更新要取决于setState是否在Async的渲染过程中，因为他会进入到异步调度过程。如果setState处于我们某个生命周期中，暂时不会BatchUpdate参与，因为组件要尽早的提前渲染。

  React中的diff算法实现流程：
  1、dom结构发生的改变，直接卸载重新创建
  2、dom结构一样，不会卸载，但是会更新变化得内容
  3、所有同一层得子节点都可以通过key来区分，同时遵循前2条规则，key的是否存在直接影响了diff算法的复杂度，在没加key的情况下会暴力前2条策略更新，加了key会优化diff算法

  React会逐个对节点进行更新，转换到目标节点。而最后插入新的节点，涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作，而如果给每个节点添加key标识，那么react优先采用移动的方式，能够找到正常的位置去插入新的节点。

  然后新版本是fiber调度了，接着说fiber相关细节
  */
}

// fiber的理解
{
  /* 
  fiber
    浏览器API requestIdleCallback
    react利用requestAnimationFrame来计算剩余时间来模拟requestIdleCallback从而实现时间分片
    利用window.performance.now()得到准确的浏览器时间
    MessageChannel创建通信管道 发送消息数据完成消息传递

  fiber流程
    获取系统当前时间 hasNativePerformanceNow
    设定任务的优先级
    根据优先级设置任务的过期时间
    根据这些优先级和过期时间进行双向链表推导
    scheduleHostCallbackIfNeeded 具体的进行调度任务
    如果时间没有过期的话
    requestAnimationFrameWithTimeout(animationTick) 压帧 （requestAnimationFrame 模拟 requestIdleCallback）
    requestAnimationFrameWithTimeout就是按照每一帧的空闲时间来计算 按照1000/30来计算

  调度算法 任务优先级
    ImmediatePriority = 1 最高任务优先级
    UserBlockingPriority = 2 用户阻塞优先级
    NormalPriority = 3 普通优先级
    LowPriority = 4 低优先级
    IdlePriority = 5 空闲优先级

  暴露API
    unstable_scheduleCallback 最低优先级任务
    flushSync 同步任务最高优先级任务
  */
}

// react组件生命周期装载、更新、销毁三个阶段分别都有哪些？
{
  /* 
  react16+ 后有三个生命周期被废除（官方计划在17版本完全删除这个三个函数）
  componentWillMount
  componentWillReceiveProps
  componentWillUpdate
  只保留UNSAVE_前缀得三个函数，目的是为了向下兼容！

  目前16.8+的新生命周期分为三个阶段：挂载阶段，更新阶段，卸载阶段
  挂载阶段：
  1、constructor：构造函数，最先被执行，通常在构造函数里，初始化state，或给定义方法绑定this
  2、static getDerivedStateFromProps(nextProps, prevState)
  这是一个静态方法，当props变化的时候，想要修改state，可以使用这个方法。它应返回一个对象来更新 state，返回 null不更新。
  3、render是纯函数，只返回渲染的东西
  4、componentDidMount：组件挂载之后调用，此时我们可以获取DOM操作，服务端请求，订阅等，但是要记得在componentWillUnMount中取消订阅
   
  更新阶段：
  1、getDerivedStateFromProps：此方法在更新挂载阶段可能被调用
  2、shouldComponentUpdate(prevProps, nextState) 有两个参数，表示新的属性props和变化之后的状态state，返回一个布尔值，true表示会触发重新渲染，false表示不会；默认返回true，通常可以利用这个生命周期优化react渲染
  3、render更新阶也会触发
  4、getSnapshotBeforeUpdate(prevProps,PrevState) 这个方法在render之后，componentDidUpdate之前调用，有两个参数prevProps和prevState，表示之前的属性props和之前状态state，这个函数有一个返回值，会作为第三个参数传递给componentDidUpdate,如果你不需要返回值，可以返回null，这个方法必须配合componentDidUpdate使用
  5、componentDidUpdate(prevProps,prveState,snapshot)该方法在getSnapshotBeforeUpdate之后被调用，有三个参数，之前的props，之前的state和snapshot；第三个参数是getSnapshotBeforeUpdate的返回值；如果触发某些回掉函数时需要用到DOM元素的状态，则将对比或计算的过程迁移至getSnapshotBeforeUpdate，然后在componentDidUpdate中统一触发回掉或状态更新

  卸载阶段：
  componentWillUnMount当组件卸载或销毁是调用，在这个函数里面我们可以清除定时器，取消网络请求，清理无用DOM元素等垃圾清理工作
  */
}

// react中调用this.setState之后，react都做了哪些操作？怎么拿到改变后的值？
{
  /* 
  setState之后，如果当前处于batchUpdate（批量更新机制）那么会把组件放入dirtyComponents队列，等待本次batchUpdate结束后合并成一个新state，再更新。
  如果当前不处于batchUpdate（批量更新机制）. 那么组件直接进入更新。 更新的时候先进入ShouldComponentUpdate的判断，如果需要进行更新，那么就进入diff，diff生产一个patch（补丁），然后更新组件

  生命周期、合成事件属性批量更新机

  patch方法接收两个参数(node, patches)，一个是要打补丁的元素，另一个就是所要打的补丁了
  此方法内通过walk方法获取所有的子节点，给子节点也进行先序深度优先遍历，递归walk，如果当前的补丁是存在的，那么就对其打补丁(doPatch)
  https://juejin.im/post/5c8e5e4951882545c109ae9c
  */
}

// react父子组件是如何通信的？兄弟组件呢？
{
  /* 
    父组件向子组件通信: props
    子组件向父组件通信: 回调函数/自定义事件
    跨级组件通信: 层层组件传递props/context, 或是可以使用redux
    没有嵌套关系组件之间的通信: 自定义事件

    改变this指向的方式有哪些？实现一个bind
    call() 方法可以指定this 的指向（即函数执行时所在的的作用域），然后再指定的作用域中，执行函数
    call 方法的参数，应该是对象obj,如果参数为空或null,undefind,则默认传参全局对象
    apply() 和call 作用类似，也是改变this 指向，然后调用该函数，唯一区别是apply 接收数组作为函数执行时的参数

    bind() 用于将函数体内的this绑定到某个对象，然后返回一个新函数
  */
}

// 单向数据流有什么好处
{
}

// redux基础层面分析述说
{
  /* 
  createStore
    getSteate, dispatch, subscribe subscribe 返回一个unsubsribe卸载函数
    发布订阅模式， 利用subscribe监听每个组件数据发生的变化

  applyMiddleware
    中间件 利用改写dispatch实现

  react-redux
    一个HOC 封装redux暴露给组件
  */
}

// 了解redux么，说一下redux是什么，以及redux有什么缺点
{
  /* 
  redux是响应式数据编程，借鉴flux思想架构。。。视图通过action 指定需要更新的数据，然后通过reducer具体更新整合新的store。。。最终返回视图view层

  在reducer层之上还设计了一层midd中间层，用于处理异步事件，因为整体action>reducer>view是同步的
  */
}

// react-router怎么传递数据的，另外hash路由和普通路由有什么区别？
{
  /* 
  react-router 利用react context来做数据传递

  hashRouter 监听hash的变化 window.addEventListener('hashchange',() => {})

  historyRouter 监听浏览器历史栈popstate信息变化 window.addEventListener('popstate', e => this.handler())
  */
}

// useCallback和useMemo的区别
{
  /* 
  useCallBack它是缓存的是函数，但函数内部如果有高消耗性能计算就要用useMemo或是返回一个函数中计算，如果useCallback返回的是一个函数等同于useMemo
  useMemo它缓存的是数值或是组件，高耗时计算在些组件包裹后，只会返回最终得到的结果，如果是组件包裹它只会渲染自己监听的值，变化后才会渲染
  */
}

// 在react中以下setState后输出的是什么
{
  class AgencyManagement extends Component {
    constructor(props) {
      super(props);
      this.state = {
        count: 1,
      };
    }
    componentDidMount() {
      new Promise((resolve) => {
        this.setState({
          count: this.state.count + 1,
        });
        console.log(this.state.count);

        this.setState({
          count: this.state.count + 1,
        });
        console.log(this.state.count);
        resolve();
      }).then((res) => {
        this.setState({
          count: this.state.count + 1,
        });
        console.log(this.state.count);
        this.setState({
          count: this.state.count + 1,
        });
        console.log(this.state.count);
      });
    }
  }
}

// react中的setState是同步的还是异步的？
{
  /* 
  本身是同步的，是否处在异步，要看什么情况下：
  react有一个批量更新机制，如果处在批量更新机制下是异步的，反之是同步的。
  那么什么情况下处于批量更新状态呢，比如react的合成事件，生命周期等，处于批量更新情况下这个方法有个合并的过程
  什么情况下是非批量更新呢，比如js的宏任务settimeout 原声点击事件。
  */
}

// 什么是react？ react的主要特点是什么？
{
  /* 
  React 是一个开源前端 JavaScript 库，用于构建用户界面，尤其是单页应用程序
  react是实现web components组件化的一个框架库，是MVC模式，它的特点是单向数据流，JSX模板（css、js写一块），
  */
}

// 动态挂载redux中的state实现技术总结
{
  /* 首先创建configureStore文件用来一个创建store的函数方法
  1、该方法内applyMiddleware()传入所有需要用到的中间件（用到saga和routerMiddleware）
  2、声明处理store函数，该函数处理要过滤掉一些store中的值，返回新的store。
  3、另外在声明createReucer函数（独立文件），用来合并过滤好的store，里面主要是store持久化合并，使用了redux-persist库的persistCombineReducers，配置持久化。
  4、此时使用redux的createStore创建一个store，传入createReducer合并后的store，初始化的值、以及compose合并的midd中间层，最终返回一个完整的store。

  5、此是关键的动态挂载开始，对着store上加入二个空对象injectedReducers，injectedSagas用于后期动态挂载值用。
  6、在就是对store挂载二个中间件更新方法，
  一个是 store.runSaga = sagaMiddleware.run; // saga异步方法
  另一个是 store.persistor = persistor; // 持久化更新方法
  7、下面就是正常挂载在app.js中，在根组件最外层Proiver注入store

  创建store，并暴露完相应方法后，动态注入到相应方法内，还需要单独封装一个方法进行处理一下。
  1、创建injectReducer函数方法，用于挂载store/saga到redux中，传入二参数key/reducer，通过redux暴露的ReactReduxContext获取到store执行上下文

  2、获取到store执行上下文，redux有暴露一个替换之前store的方法replaceReducer，通过此方法把之前的store替换更新

  saga挂载也是类似一样方式，但最后通过store.runSaga调用更新的
  */
}

// 什么是JSX？
{
  /* 
  JSX是JavaScript中的一种语法扩展。是React组件编写UI逻辑的语言扩展著作权归作者所有。
  */
}

// JSX 与 模板引擎的区别
{
  /* 
  JSX中的{}让其拥有了类似模板引擎的功能，但它比模板引擎更强大，因为它是javascript语法的延伸：
  你可以把它作为一个变量的值，在if和for循环中使用它
  也能把它当做参数传递给函数
  还能作为函数的返回值返回。
  */
}

// JSX的原理？即JSX是经过怎么样的转化变成页面的元素的。
{
  /* 
  浏览器不能直接执行包含JSX代码的JavaScript文件。它们必须首先转换成普通的JavaScript。需要一个叫做转置的过程。

  通过@babel/preset-react插件把JSX语法转换成AST树(babel支持的结构)，接着由babel转换处理生成相应的js代码

  像这样代码 <h1 class="title">Hello World!</h1>
  转成如下ast对象
  { tag: 'div', attrs: {className: 'title'}, children: 'Hello World!' }
  */
}

// react中state和props的区别？
{
  /* 
  state和props都是JS中的普通对象，用来保存信息的。这是相同点
  不同点如下：
  props 是传递给组件的（类似于函数的形参），是不可修改的，所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。所以传递给组件只能用于读取

  state 是在组件内被组件自己管理的（类似于在一个函数内声明的变量），是可被修改的。
  */
}

// react元素和组件有什么区别？
{
  /* 
  元素：一个Element是一个简单的对象，它描述了你希望在屏幕上以 DOM 节点或其他组件的形式呈现的内容。Elements在它们的属性中可以包含其他Elements。创建一个 React 元素是很轻量的。一旦元素被创建后，它将不会被修改。
  React Element 的对象表示如下：
  const element = React.createElement(
    'div',
    {id: 'login-btn'},
    'Login'
  )
  上面的React.createElement()函数会返回一个对象。
  {
    type: 'div',
    props: {
      children: 'Login',
      id: 'login-btn'
    }
  }
  最后使用ReactDOM.render()方法渲染到 DOM： <div id='login-btn'>Login</div>

  组件：组件可以用多种不同方式声明。它可以是一个含有render()方法的类。或者，在简单的情况中，它可以定义为函数。无论哪种情况，它都将 props 作为输入，并返回一个 JSX 树作为输出：
  const Button = ({ onLogin }) => <div id={'login-btn'} onClick={onLogin} />
  然后 JSX 被转换成React.createElement()函数：
  const Button = ({ onLogin }) => React.createElement(
    'div',
    { id: 'login-btn', onClick: onLogin },
    'Login'
  )
  */
}

// 什么是 PureComponents?
{
  /* 
  React.PureComponent与React.Component完全相同，只是它为你处理了shouldComponentUpdate()方法。
  当属性或状态发生变化时，PureComponent 将对属性和状态进行浅比较。另一方面，一般的组件不会将当前的属性和状态与新的属性和状态进行比较。因此，在默认情况下，每当调用shouldComponentUpdate时，默认返回 true，所以组件都将重新渲染。
  */
}

// 如何在 React 中创建组件？
{
  /* 
  方式一：使用纯函数创建组件，第一参数为props，通过return返回显示渲染页面的组件
  function Greeting({ message }) {
    return <h1>{`Hello, ${message}`}</h1>
  }

  方式二：使用类的方式声明一个组件
  class Greeting extends React.Component {
    render() {
      return <h1>{`Hello, ${this.props.message}`}</h1>
    }
  }
  */
}

// 函数组件与类组件的差异？
{
  /* 
  函数组件获取已经渲染过的值（Function components capture the rendered values.）
  */
}

// 什么时候需要使用 refs？
{
  /* 
  1、需要调用子组件上面的方法，会用到refs
  2、需要直接操作组件上面的dom时候

  String类型不支持挂载到refs上面
  */
}

// createElement 和 cloneElement 有什么区别？
{
  /* 
  JSX 语法就是用 React.createElement()来构建 React 元素的。
  它接受三个参数，第一个参数可以是一个标签名。如 div、span，或者 React 组件。第二个参数为传入的属性。第三个以及之后的参数，皆作为组件的子组件。
  React.createElement(type, [props], [...children]);

  React.cloneElement()与 React.createElement()相似，不同的是它传入的第一个参数是一个 React 元素，而不是标签名或组件。新添加的属性会并入原有的属性，传入到返回的新元素中，而旧的子元素将被替换。将保留原始元素的键和引用。
  React.cloneElement(element, [props], [...children]);
  */
}

// useEffect父子组件中同时用到，执行顺序哪个先？先哪后执行
{
  /* 
  useLayoutEffect()永远比useEffect()先执行，即便在你的代码中useEffect()是写在前面的。所以useLayoutEffect()才是事实上和componentDidUpdate()/componentDidMount()平起平坐的存在。

  useEffect()会在父子组件的componentDidUpdate()/componentDidMount()都触发之后才被触发。当父子组件都用到useEffect()时，子组件中的会比父组件中的先触发。
  */
}

// 什么是 Fragments ?
{
  /* 
  Fragments可以让你聚合一个子元素列表，而无需向 DOM 添加额外节点。
  通过不创建额外的 DOM 节点，Fragments 更快并且使用更少的内存。这在非常大而深的节点树时很有好处。
  一些 CSS 机制如Flexbox和CSS Grid具有特殊的父子关系，如果在中间添加 div 将使得很难保持所需的结构。
  在 DOM 审查器中不会那么的杂乱。
  */
}

// 什么是受控组件、非受控组件?
{
  /* 
  受控组件：在随后的用户输入中，能够控制表单中输入元素的组件被称为受控组件，即每个状态更改都有一个相关联的处理程序。
  例如，我们使用下面的 handleChange 函数将输入框的值转换成大写：
  handleChange(event) {
    this.setState({value: event.target.value.toUpperCase()
  })

  非受控组件：非受控组件是在内部存储其自身状态的组件，当需要时，可以使用 ref 查询 DOM 并查找其当前值。这有点像传统的 HTML。
  */
}

// 什么是 "key" 属性，在元素数组中使用它们有什么好处?
{
  /* 
  这里react团队对传统diff算法优化主要基于三个策略:
  1.相同层级的节点但DOM结构发生改变-----直接卸载并重新creat
  2.相同层级节点但是DOM结构一样-----不会卸载,但是会update
  3.所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点
  */
}
